// =============================================================================
//  STATS - Statistical Analysis Tools, v.0.01
//  Copyright (C) 2012  Marco Vettigli
// -----------------------------------------------------------------------------
//
// This program  is free software:  you can  redistribute it and/or  modify it
// under the terms of the  GNU General Public License as published by the Free
// Software Foundation,  either version 3 of the License,  or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY;  without even  the  implied  warranty  of  MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License  for
// more details.
//
// You  should  have received a copy of the  GNU General Public License  along
// with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// == module ===================================================================
//
// UTILS.CPP
// The class contains several utility function to handle data analysis and
// manipulation.
//
// Author: Marco Vettigli
//
// The class Utils contains a number of utility function to be used on StatS
// object to manipulate properties of retrieving cumulate properties. It
// contains all the function to parse source file. All statistical functions
// will be placed here.
//
//
// == implementation ===========================================================
//
// The function 'parseCSV' construct a STable object starting from a comma
// separated value file. It used 'parseLine' aid function to actually parse
// the data; it works on tokens identified by delimiter and quote character,
// 'delimiter' separates tokens while 'quotes' is used to define complex
// string elements.
//
// =============================================================================

// == include directives =======================================================

#include <QTextStream>
#include <QFileInfo>
#include <QFile>
#include <QDebug>
#include <cmath>
#include "utils.h"

// == constructors and destructors =============================================

// == getters ==================================================================

// == setters ==================================================================

// == inspectors ===============================================================

// == accessor function definitions ============================================

// == static variables =========================================================

// == static funtion definitions ===============================================

StatS::STable* StatS::Utils::parseCsv(const QString filename,
                                      QChar delimiter,
                                      QChar quotes,
                                      bool firstNames)
{
    QFile* file = new QFile(filename);
    // check if filename exist and open it
    if (!file->exists() || !file->open(QFile::ReadOnly)) return 0;
    // open the stream
    QTextStream* in = new QTextStream(file);
    // define local variables to parse CSV file
    QVector<QString> parsed_line;
    QString line;    
    StatS::STable* table = new StatS::STable(QFileInfo(filename).baseName());
    // parse first line of the file to define column number
    int num_cols = 0;
    if (!in->atEnd() && !(line = in->readLine()).isEmpty()) {
        num_cols = parseLine(parsed_line, line, delimiter, quotes);
        // browse the tokens to assign create columns
        for(int i = 0; i < parsed_line.size(); i++) {
            // store the value as column name..
            table->insertColumn(table->columnCount(),
                                StatS::SColProperty::SStrings,
                                firstNames ? parsed_line[i]
                                           : QString());
            // ..or value depending on 'firstNames'
            QModelIndex index = table->index(0, i + 1, QModelIndex());
            if (!firstNames) table->setData(index, parsed_line[i]);
        }
    }
    // delete first columns created by default on new STable
    table->removeColumns(0, table->DEFAULT_COLS);
    // parse the rest of file to add table rows
    int parsed_rows = firstNames ? 0 : 1;
    while (!in->atEnd() && !(line = in->readLine()).isEmpty()) {
        // check if token number is consistent with column number
        if (parseLine(parsed_line, line, delimiter, quotes) == num_cols) {
            table->insertRow(table->rowCount());
            for(int i = 0; i < parsed_line.size(); i++) {
                QModelIndex index = table->index(parsed_rows, i, QModelIndex());
                table->setData(index, parsed_line[i]);
            }
            parsed_rows++;
        } else {
            // column-data number inconsistency
            file->close();
            delete in;
            delete table;
            return 0;
        }
    }
    // delete last rows created by default on new STable
    table->removeRows(parsed_rows, firstNames ? table->DEFAULT_ROWS :
                                                table->DEFAULT_ROWS -1);
    // look for column convertion to SDoubles
    for (int i = 0; i < table->columnCount(); i++) {
        if (table->isColumnConvertable(i, StatS::SColProperty::SDoubles))
            table->convertColumn(i, StatS::SColProperty::SDoubles);
    }
    // close the file, QTextStream and return
    file->close();
    delete in;
    return table;
}

StatS::STable* StatS::Utils::parseStatS(const QString filename)
{    
    // !!! the code is dirty because Qt doesn't
    // implement exception handling properly !!!
    // -----------------------------------------
    // define variables
    QFile* file = 0;
    QTextStream* in = 0;
    StatS::STable* table = 0;
    // check if the file exists and open it
    file = new QFile(filename);
    if (!file->exists() || !file->open(QFile::ReadOnly)) {
        delete file;
        return 0;
    }
    in = new QTextStream(file);
    // define local variables to parse StatS file
    QString line;
    QVector<QString> parsed_properties;
    QVector<QString> parsed_line;
    // parse table name
    if (in->atEnd()) {
        file->close();
        delete file;
        delete in;
        return 0;
    }
    line = in->readLine();
    if (parseLine(parsed_line, line, QChar(','), QChar('"')) != 1) {
        file->close();
        delete file;
        delete in;
        return 0;
    }
    table = new StatS::STable(line);
    table->setFilename(filename);
    // parse column name
    if (in->atEnd()) {
        file->close();
        delete file;
        delete in;
        return 0;
    }
    line = in->readLine();
    int cols = parseLine(parsed_line, line, QChar(','), QChar('"'));
    // parse column properties
    if (in->atEnd()) {
        file->close();
        delete file;
        delete in;
        return 0;
    }
    line = in->readLine();
    if (parsePropertyLine(parsed_properties, line) != cols) {
        file->close();
        delete file;
        delete in;
        return 0;
    }
    // remove redundand columns and rename existing ones
    if (table->columnCount() > cols)
        table->removeColumns(cols - 1, table->columnCount() - cols);
    for (int i = 0; i < table->columnCount(); i++)
        table->setHeaderData(i, Qt::Horizontal, parsed_line[i]);
    // insert remaining columns
    for (int i = table->columnCount(); i < cols; i++)
        table->insertColumn(i, StatS::SColProperty::SStrings,
                            parsed_line[i]);
    // set column properties
    for (int i = 0; i < cols; i++) {
        if (parseLine(parsed_line, parsed_properties[i],
                      QChar(','), QChar('"')) != 5) {
            file->close();
            delete file;
            delete in;
            return 0;
        }
        table->convertColumn(i, StatS::SColProperty::ArrayType(
                                 parsed_line[0].toInt()));
        table->setColumnDescription(i, parsed_line[1]);
        table->setColumnUnits(i, parsed_line[2]);
        table->setColumnLsl(i, parsed_line[3].toDouble());
        table->setColumnUsl(i, parsed_line[4].toDouble());
    }
    // parse row properties
    if (in->atEnd()) {
        file->close();
        delete file;
        delete in;
        return 0;
    }
    line = in->readLine();
    int rows = parsePropertyLine(parsed_properties, line);
    // remove redundand rows
    if (table->rowCount() > rows)
        table->removeRows(0, table->rowCount() - rows);
    // add remaining rows
    if (rows > table->rowCount())
        table->insertRows(0, rows - table->rowCount());
    // set row properties
    for (int i = 0; i < rows; i++) {
        if (parseLine(parsed_line, parsed_properties[i],
                      QChar(','), QChar('"')) != 4) {
            file->close();
            delete file;
            delete in;
            return 0;
        }
        table->setRowMarker(i, StatS::SRowProperty::RowMarker(
                                parsed_line[0].toInt()));
        table->setRowColor(i, QColor(parsed_line[1]));
        table->setRowSize(i, parsed_line[2].toInt());
    }
    // parse data
    for (int i = 0; i < cols; i++) {
        if (in->atEnd()) {
            file->close();
            delete file;
            delete in;
            return 0;
        }
        line = in->readLine();
        if (parseLine(parsed_line, line, QChar(','), QChar('"')) != rows) {
            file->close();
            delete file;
            delete in;
            return 0;
        }
        for (int j = 0; j < rows; j++)
            table->setData(table->index(j, i), parsed_line[j]);
    }
    // close file and dispose objects
    file->close();
    delete file;
    delete in;
    // return generated table
    return table;
}

bool StatS::Utils::parseClipboard(QVector<QVector<QString> >& table,
                                  const QString& clipboard)
{
    // define variables
    int length = clipboard.length();
    QVector<QString> line;
    QString token;
    QChar c;
    // browse the string looking for delimiter chars
    for (int i = 0; i < length; i++) {
        c = clipboard.at(i);
        // end of token
        if (c == QChar('\t')) {
            line.push_back(token);
            token = "";
            continue;
        }
        // end of line
        if (c == QChar('\n')) {
            line.push_back(token);
            table.push_back(line);
            line.clear();
            token = "";
            continue;
        }
        // add char to current token
        token.append(c);
    }
    // check if tabular data are consistent
    if (table.size() == 0) return false;
    int cols = table[0].size();
    foreach (QVector<QString> row, table)
        if (row.size() != cols) return false;
    // returns
    return true;
}

int StatS::Utils::getPairNumber(StatS::SDoubles *array1,
                                StatS::SDoubles *array2)
{
    // check if array size is the same
    if (array1->size() != array2->size()) return -1;
    // count number of not null pairs
    int pairs = 0;
    for (int i = 0; i < array1->size(); i++)
        if (!array1->isNull(i) && !array2->isNull(i)) pairs++;
    return pairs;
}

QList<int> StatS::Utils::getRangeList(int to, int from)
{
    QList<int> list;
    // check if 'from' is greater than 'to' and swap them
    if (to < from) {
        int temp = to;
        to = from;
        from = temp;
    }
    // fill and return the list
    for (int i = from; i <= to; i++)
        list.append(i);
    return list;
}

// == private function definitions =============================================

int StatS::Utils::parsePropertyLine(QVector<QString>& parsed_line,
                                    QString& line)
{
    int num_tokens = 0;
    QChar c;
    QString token;
    bool token_flag = false;
    // clear vector from previous data..
    parsed_line.clear();
    // ..and scan the line looking for delimiters outside quotes
    for (int i = 0; i < line.size(); i++) {
        c = line.at(i);
        // check if character is starting a new quoting
        if (c == QChar('{')) {
            if (!token_flag) {
                token_flag = true;
                continue;
            }
        }
        if (c == QChar('}')) {
            if (token_flag) {
                token_flag = false;
                continue;
            }
        }
        // check if character is starting a new token
        if (c == QChar(',')) {
            if (!token_flag) {
                // store the token and continue the loop
                parsed_line.push_back(token);
                token = "";
                num_tokens++;
                continue;
            }
        }
        // append the character to current token buffer
        token.append(c);
    }
    // store the last parsed token..
    parsed_line.push_back(token);
    num_tokens++;
    // ..and return the number
    return num_tokens;
}

int StatS::Utils::parseLine(QVector<QString>& parsed_line,
                            QString& line,
                            QChar delimiter,
                            QChar quotes)
{
    int num_tokens = 0;
    QChar c;
    QString token;
    bool token_flag = false;
    // clear vector from previous data..
    parsed_line.clear();
    // ..and scan the line looking for delimiters outside quotes
    for (int i = 0; i < line.size(); i++) {
        c = line.at(i);
        // check if character is starting a new quoting
        if (c == quotes) {
            // consider double quoting as meaning quote character
            if (i != line.size() && line[i + 1] == quotes) {
                token.append(c);
                i++;
            }
            // else (un)flag the token restart loop
            else token_flag = !token_flag;
            // skip the character
            continue;
        }
        // check if character is starting a new token
        if (c == delimiter) {
            if (!token_flag) {
                // store the token and continue the loop
                parsed_line.push_back(token);
                token = "";
                num_tokens++;
                continue;
            }
        }
        // append the character to current token buffer
        token.append(c);
    }
    // store the last parsed token..
    parsed_line.push_back(token);
    num_tokens++;
    // ..and return the number
    return num_tokens;
}
